package com.yutian.springbootwebflux.util;

import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.net.URLEncoder;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;


public class OkHttpRestUtil {
    private static final Logger log = LoggerFactory.getLogger(OkHttpRestUtil.class);

    private final static int CONNECT_TIMEOUT = 1;
    private final static int READ_TIMEOUT = 1;
    private final static int WRITE_TIMEOUT = 1;

    /**
     * 使用OkHttpClient 的时候需要注意以下几点：
     * 1.最好只使用一个共享的OkHttpClient实例，将所有的网络请求都通过这个实例处理。因为每个OkHttpClient 实例都有自己的连接池和线程池，重用这个实例能降低延时，减少内存消耗，而重复创建新实例则会浪费资源。
     * 2.OkHttpClient的线程池和连接池在空闲的时候会自动释放，所以一般情况下不需要手动关闭，但是如果出现极端内存不足的情况，可以使用以下代码释放内存：
     * 链接：https://www.jianshu.com/p/a71a42f4634b
     *
     * 注：http请求出现某种错误时,http请求僵死，导致线程也不再往下执行，也不会报超时异常。之前一直认为http会有一个默认的超时时间（可能是5min），超过这个时间后会报超时异常。这个看法误导了我。
     * @return
     */
    private static volatile OkHttpClient okHttpClient;

    private static OkHttpClient newOkHttpClient() {
        if(null == okHttpClient){
            synchronized (OkHttpRestUtil.class) {
                if(null == okHttpClient){
                    okHttpClient = new OkHttpClient.Builder()
                            .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)       //设置连接超时
                            .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)          //设置读超时
                            .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)          //设置写超时
                            .retryOnConnectionFailure(true)             //只有在特定情况下，okhttp才会重试。 Okhttp默认也会自动重连，会导致网络不好的情况下怀疑自己设置的超时时间没有起作用
                            .build();
                }
            }

        }
        return okHttpClient;
    }

    public static String postJsonUrlRes(String url, String json, Map<String, String> headers){
        String res = null;
        Response resp = null;
        try {
            OkHttpClient okHttpClient = newOkHttpClient();
            MediaType jsonType = MediaType.parse("application/json; charset=utf-8");
            RequestBody body =  RequestBody.create(jsonType, json);
            Request.Builder b = new Request.Builder()
                    .url(url)
                    .post(body)
                    .addHeader("Content-Type", "application/json");
            if(MapUtils.isNotEmpty(headers)){
                for(Map.Entry<String, String> entry : headers.entrySet()){
                    b.addHeader(entry.getKey(), entry.getValue());
                }
            }
            Request request = b.build();
            Call call = okHttpClient.newCall(request);
            resp = exec(url, call, json);
            res = getRes(url, resp, json);
        } finally {
            IOUtils.closeQuietly(resp);
        }

        return res;
    }

    public static String postFormUrlRes(String url, Map<String, Object> params, Map<String, String> headers){
        String res = null;
        Response resp = null;
        try {
            OkHttpClient okHttpClient = newOkHttpClient();
            MultipartBody.Builder requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM);
            if (MapUtils.isNotEmpty(params)) {
                // params 里面是请求中所需要的 key 和 value
                for (Map.Entry<String, Object> entry : params.entrySet()) {
                    if (null == entry.getValue()) {
                        continue;
                    }
                    requestBody.addFormDataPart(entry.getKey(), Objects.toString(entry.getValue()));
                }
            }
            Request request = new Request.Builder()
                    .url(url)
                    .post(requestBody.build())
                    .build();
            Call call = okHttpClient.newCall(request);
            resp = exec(url, call, params==null ? null : params.toString());
            res = getRes(url, resp, params==null ? null : params.toString());
        } finally {
            IOUtils.closeQuietly(resp);
        }

        return res;
    }

    @Deprecated
    public static String postUrlRes(String url, Map<String, Object> params, Map<String, String> headers){
        String res = null;
        Response resp = null;
        String enc = "UTF-8";
        try {
            OkHttpClient okHttpClient = newOkHttpClient();
            StringBuilder urlSuffix = new StringBuilder(url);
            if(MapUtils.isNotEmpty(params)){
                urlSuffix.append("?");
                for(Map.Entry<String, Object> entry : params.entrySet()){
                    try {
                        urlSuffix.append(entry.getKey()).append("=");
                        if(null != entry.getValue()){
                            urlSuffix.append(URLEncoder.encode(entry.getValue().toString(), enc));
                        }
                        urlSuffix.append("&");
                    } catch (Exception e) {
                        throw new RuntimeException("url.param.urlencode.error");
                    }
                }
                url = urlSuffix.substring(0, urlSuffix.length()-1);
            }
            RequestBody body = RequestBody.create(null, new byte[0]);
            Request.Builder b = new Request.Builder()
                    .url(url)
                    .post(body);
            if(MapUtils.isNotEmpty(headers)){
                for(Map.Entry<String, String> entry : headers.entrySet()){
                    b.addHeader(entry.getKey(), entry.getValue());
                }
            }
            Request request = b.build();
            Call call = okHttpClient.newCall(request);
            resp = exec(url, call, null);
            res = getRes(url, resp, null);
        } finally {
            IOUtils.closeQuietly(resp);
        }

        return res;
    }

    public static String getUrlRes(String url, Map<String, Object> params, Map<String, String> headers){
        String res = null;
        Response resp = null;
        String enc = "UTF-8";
        try {
            OkHttpClient okHttpClient = newOkHttpClient();
            StringBuilder urlSuffix = new StringBuilder(url);
            if(MapUtils.isNotEmpty(params)){
                urlSuffix.append("?");
                for(Map.Entry<String, Object> entry : params.entrySet()){
                    try {
                        urlSuffix.append(entry.getKey()).append("=");
                        if(null != entry.getValue()){
                            urlSuffix.append(URLEncoder.encode(entry.getValue().toString(), enc));
                        }
                        urlSuffix.append("&");
                    } catch (Exception e) {
                        throw new RuntimeException("url.param.urlencode.error");
                    }
                }
                url = urlSuffix.substring(0, urlSuffix.length()-1);
            }
            Request.Builder b = new Request.Builder()
                    .url(url)
                    .get();
            if(MapUtils.isNotEmpty(headers)){
                for(Map.Entry<String, String> entry : headers.entrySet()){
                    b.addHeader(entry.getKey(), entry.getValue());
                }
            }
            Request request = b.build();
            Call call = okHttpClient.newCall(request);
            resp = exec(url, call, null);
            res = getRes(url, resp, null);
        } finally {
            IOUtils.closeQuietly(resp);
        }

        return res;
    }

    private static Response exec(String url, Call call, String reqbody) {
        Response resp = null;
        if(StringUtils.trimToEmpty(reqbody).length() > 4000){
            // 避免日志超长
            reqbody = reqbody.substring(0, 1000) + "...";
        }
        StopWatch watch = StopWatch.createStarted();
        try {
            resp = call.execute();
        } catch (Exception e) {
            log.error("url.cannot.access, url={}, reqbody={}", url, reqbody, e);
            throw new RuntimeException("url.cannot.access");
        } finally {
            long time = watch.getTime(TimeUnit.MILLISECONDS);
            log.info("url.exec, url={}, cost time(ms) = {}", url, time);
        }
        if(!resp.isSuccessful()){
            log.error("url.response.httpstatus.error, url={}, reqbody={}, code={}", url, reqbody, resp.code());
            if(resp.code() >= 400 && resp.code() < 500) {
                throw new RuntimeException("url.response.network.error");
            }else{
                throw new RuntimeException("url.response.httpstatus.error");
            }
        }
        return resp;
    }

    private static String getRes(String url, Response resp, String reqbody) {
        String res = null;
        if(StringUtils.trimToEmpty(reqbody).length() > 4000){
            // 避免日志超长
            reqbody = reqbody.substring(0, 1000) + "...";
        }
        if(null != resp.body()){
            try {
                res = resp.body().string();
            } catch (Exception e) {
                log.error("url.response.body.read.error, url={}, reqbody={}", url, reqbody, e);
                throw new RuntimeException("url.response.body.read.error");
            }
        }
        log.info("url={}, reqbody={}, response={}", url, reqbody, StringUtils.abbreviate(res, 1000));
        return res;
    }
}
